/***************************************************************************\
 * The Mattise Kernel: Userland Software									*
 * Copyright 2007 Matthew Iselin											*
 * Licensed under the GPL													*
 *																			*
 * main.c																	*
 *																			*
 * Entrypoint for the TTY driver (tty in Mattise = console out, keyboard in	*
 *																			*
\***************************************************************************/

/***************************************************************************
 * Includes
 ***************************************************************************/
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/time.h>

#include <process.h>
#include <portio.h>
#include <mmsg.h>
#include <vfs.h>

/***************************************************************************
 * Types
 ***************************************************************************/

union uSetLED {
	uint8_t pdata;
	struct {
		uint32_t slock : 1;
		uint32_t nlock : 1;
		uint32_t clock : 1;
		uint32_t rsvd1 : 1;
		uint32_t rsvd2 : 1;
		uint32_t rsvd3 : 1;
		uint32_t rsvd4 : 1;
		uint32_t rsvd5 : 1;
	} pstruct;
};

/***************************************************************************
 * Globals
 ***************************************************************************/

// keyboard input buffer (maximum of 1024 characters, once exceeded no more characters are added
volatile unsigned char keybuff = 0;

// number of characters in the buffer at the moment
volatile int nkeys = 0;

// US keyboard layout (scancode-based)
const unsigned char kbdus[128] =
{
	0,		/** entry 0 **/
	27,
	'1',	/** entry 2 **/
	'2',
	'3',
	'4',
	'5',
	'6',
	'7',
	'8',
	'9',
	'0',	/** entry 11 **/
	'-',
	'=',
	'\b',	/** entry 14 **/
	'\t',
	'q',	/** entry 16 **/
	'w',
	'e',
	'r',
	't',
	'y',
	'u',
	'i',
	'o',
	'p',	/** entry 25 **/
	'[',
	']',
	'\n',	/** entry 28 **/
	0,		/** entry 29 - "Ctrl" **/
	'a',	/** entry 30 **/
	's',
	'd',
	'f',
	'g',
	'h',
	'j',
	'k',
	'l',	/** entry 38 **/
	';',	/** entry 39 **/
	'\'',
	'`',
	0,		/** entry 42 - left shift **/
	'\\',
	'z',	/** entry 44 **/
	'x',
	'c',
	'v',
	'b',
	'n',
	'm',	/** entry 50 **/
	',',
	'.',
	'/',
	0,		/** entry 54 - right shift **/
	'*',
	0,		/** entry 56 - alt **/
	' ',	/** entry 57 - space **/
    0,		/** entry 58 - caps lock **/
    0,		/** entry 59 - F1 **/
    0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,		/** entry 68 - F10 **/
	0,		/** entry 69 - num lock **/
	0,		/** entry 70 - scroll lock **/
	0,		/** entry 71 - home key **/
	0,		/** entry 72 - up arrow **/
	0,		/** entry 73 - page up **/
	'-',
	0,		/** entry 75 - left arrow **/
	76,		/** entry 76 - ? **/
	0,		/** entry 77 - right arrow **/
	'+',
	0,		/** entry 79 - end */
	0,		/** entry 80 - down arrow **/
	0,		/** entry 81 - page down **/
	0,		/** entry 82 - insert key **/
	0,		/** entry 83 - delete */
	0,
	0,
	0,
	0,		/** entry 87 - F11 */
	0,		/** entry 89 - F12 */
	0,		/** > 89 = nothing **/
};

// US keyboard layout (scancode-based)
const unsigned char kbdus_shift[128] =
{
	0,		/** entry 0 **/
	27,
	'!',	/** entry 2 **/
	'@',
	'#',
	'$',
	'%',
	'^',
	'&',
	'*',
	'(',
	')',	/** entry 11 **/
	'_',
	'+',
	'\b',	/** entry 14 **/
	'\t',
	'Q',	/** entry 16 **/
	'W',
	'E',
	'R',
	'T',
	'Y',
	'U',
	'I',
	'O',
	'P',	/** entry 25 **/
	'{',
	'}',
	'\n',	/** entry 28 **/
	0,		/** entry 29 - "Ctrl" **/
	'A',	/** entry 30 **/
	'S',
	'D',
	'F',
	'G',
	'H',
	'J',
	'K',
	'L',	/** entry 38 **/
	':',	/** entry 39 **/
	'"',
	'~',
	0,		/** entry 42 - left shift **/
	'|',
	'Z',	/** entry 44 **/
	'X',
	'C',
	'V',
	'B',
	'N',
	'M',	/** entry 50 **/
	'<',
	'>',
	'?',
	0,		/** entry 54 - right shift **/
	'*',
	0,		/** entry 56 - alt **/
	' ',	/** entry 57 - space **/
    0,		/** entry 58 - caps lock **/
    0,		/** entry 59 - F1 **/
    0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	0,		/** entry 68 - F10 **/
	0,		/** entry 69 - num lock **/
	0,		/** entry 70 - scroll lock **/
	0,		/** entry 71 - home key **/
	0,		/** entry 72 - up arrow **/
	0,		/** entry 73 - page up **/
	'-',
	0,		/** entry 75 - left arrow **/
	76,		/** entry 76 - ? **/
	0,		/** entry 77 - right arrow **/
	'+',
	0,		/** entry 79 - end */
	0,		/** entry 80 - down arrow **/
	0,		/** entry 81 - page down **/
	0,		/** entry 82 - insert key **/
	0,		/** entry 83 - delete */
	0,
	0,
	0,
	0,		/** entry 87 - F11 */
	0,		/** entry 89 - F12 */
	0,		/** > 89 = ? **/
};

/***************************************************************************
 * Functions
 ***************************************************************************/

// writes to the keyboard
void kbwrite( uint8_t d )
{
	while( 1 )
	{
		uint8_t c = mlib_inportb( 0x64 );
		if( ! ( c & 0x1 ) )
			break;
	}
	mlib_outportb( 0x60, d );
}

// reads from the keyboard
uint8_t kbread()
{
	while( 1 )
	{
		uint8_t c = mlib_inportb( 0x64 );
		if( ! ( c & 0x2 ) )
			break;
	}
	return mlib_inportb( 0x60 );
}

// IRQ1 handler thread
void keyboard_input()
{
	// temporary storage location
	uint8_t c = 0;
	
	// is shift pressed?
	int isShifted = 0;
	
	// register for the keyboard IRQ
	asm( "int $0x80" : : "a" (32), "c" (1) );
	
	// wait for the notification
	struct RecvMsgData myMsg;
	while( 1 )
	{
		if( mattiseHasMessage() )
		{
			/***** HANDLE MESSAGE RETRIEVAL *****/
			
			// get the message
			char* mbuff = mattiseRecvMessage( &myMsg );
			
			// free the data
			mattiseFreeMessage( mbuff );
			
			/***** START KEYBOARD HANDLING *****/
			
			// get the scancode
			c = kbread();
			
			// extended key
			if( c == 0xE0 || c == 0xE1 ) 
			{
				uint8_t d = kbread();
				continue;
			}
			
			// key released
			if( c & 0x80 )
			{
				// remove the 7th bit
				c ^= 0x80;
				
				// check for shift
				if( c == 54 || c == 42 )
				{
					isShifted = 0;
					continue;
				}
			}
			// key pressed
			else
			{
				// check for shift
				if( c == 54 || c == 42 )
				{
					isShifted = 1;
					continue;
				}
				
				// verify that it is a safe key
				if( kbdus[c] == 0 )
					continue; // TODO: do something special, such as add a > 0x80 key to the buffer
				
				// which key?
				if( isShifted )
					keybuff = kbdus_shift[c];
				else
					keybuff = kbdus[c];
			}
		}
	}
}

// moves the hardware cursor
void move_hw_cursor( int x, int y )
{
    uint16_t t = ( y * 80 ) + x;

	outportb( 0x3D4, 0x0F );
	outportb( 0x3D5, t );
	outportb( 0x3D4, 0x0E );
	outportb( 0x3D5, t>>8 );
}

// prints a character
int putch( char c, uint32_t* x, uint32_t* y, uint16_t* cons_fb )
{
	int tmpx = *x, tmpy = *y;
	if( c == 0 )
		return 0;
	if( c == 13 || c == 10 )
	{
		tmpy++;
		tmpx = 0;
		*x = tmpx; *y = tmpy;
		move_hw_cursor( tmpx, tmpy );
		return 0;
	}
	cons_fb[(tmpy * 80) + tmpx] = ( 0x07 << 8 ) | c;
	tmpx++;
	if( tmpx == 80 )
	{
		tmpx = 0;
		tmpy++;
	}
	move_hw_cursor( tmpx, tmpy );
	*x = tmpx; *y = tmpy;
	return 1;
}

// prints a string
int putstr( char* str, uint32_t* x, uint32_t* y, uint16_t* fb )
{
	int i;
	for( i = 0; i < strlen( str ); i++ )
		putch( str[i], x, y, fb );
	return i;
}

void memsetw( short* p, short d, int s )
{
	int i;
	for( i = 0; i < s; i++ )
		p[i] = d;
}

// Entry point
int main( int argc, char* argv[] )
{
	// message size and message buffer
	char* mbuff;
	
	// load up the keyboard input thread
	createthread( (unsigned int) keyboard_input, 2, "keyboard input thread (TTY)", 10 );
	
	// message data
	struct RecvMsgData myMsg;
	
	// mount this drive in the devfs
	struct vfs_ReqData* mountReq = (struct vfs_ReqData*) mattiseAllocMessageSz( sizeof( struct vfs_ReqData ) );
	mountReq->srcpid = getpid();
	mountReq->datlen = sprintf( mountReq->data, "tty" );
	mountReq->msgcode = VFS_MOUNT;
	
	// send off the message
	mattiseSendMessage(	2, /* todo: find the devfs in a different way */
						VFS_MOUNT,
						(char*) mountReq,
						sizeof( struct vfs_ReqData )
						);
	
	// get access to the framebuffer
	__asm__ __volatile__ ( "int $0x80" : : "a" (34) );
	
	// pointer to the framebuffer
	uint16_t* cons_fb = (uint16_t*) 0xb8000;
	
	// clear the screen
	memsetw( cons_fb, 0x0720, 80 * 25 * 2 );
	
	// x and y location of the cursor at any point
	uint32_t x = 0, y = 0;
	
	// main message loop
	while( 1 )
	{
		// do we have a message?
		if( mattiseHasMessage() )
		{		
			// get the message
			mbuff = mattiseRecvMessage( &myMsg );
			
			// get the message
			struct vfs_ReqData* req = (struct vfs_ReqData*) mbuff;
			struct vfs_ReqData* rep = (struct vfs_ReqData*) mattiseAllocMessageSz( sizeof( struct vfs_ReqData ) );
			
			// what descriptor is it?
			switch( myMsg.msgcode )
			{
				case VFS_OPEN:
				
					// return our PID
					rep->descriptor = getpid();
					mattiseSendMessage( req->srcpid, VFS_REPMSG, (char*) rep, sizeof( struct vfs_ReqData ) );
					break;
				
				case VFS_CLOSE:
				
					break;
				
				case VFS_READ:
				
					{
					// any keys?
					ragain:
					if( keybuff )
					{
						int cnt = 0;
						while( 1 )
						{
							rep->data[cnt] = keybuff;
							putch( keybuff, &x, &y, cons_fb );
							cnt++;
							if( keybuff == 13 || keybuff == 10 )
							{
								rep->data[cnt++] = '\n';
								keybuff = 0;
								break;
							}
							keybuff = 0;
							while( !keybuff );
						}
						rep->data[cnt] = 0;
						rep->datlen = cnt;
						mattiseSendMessage( req->srcpid, VFS_READ, (char*) rep, sizeof( struct vfs_ReqData ) );
					}
					else
					{
						while( !keybuff );
						goto ragain;
					}
					}
					break;
				
				case VFS_WRITE:
				
					{
					// write the buffer to the screen, return the number of chars printed
					rep->datlen = putstr( rep->data, &x, &y, cons_fb );
					mattiseSendMessage( req->srcpid, VFS_WRITE, (char*) rep, sizeof( struct vfs_ReqData ) );
					}
					break;
			}
			
			// free the message
			mattiseFreeMessage( mbuff );
		}
	}
	
	// return success
	return 0;
}
